﻿using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Security;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Web;

namespace CheckIn.UserInfoHost.Infrastructures
{
    public class WechatHelper
    {
        /// <summary>
        /// 使用 AesGcm 解密
        /// </summary>
        /// <param name="key">key32位字符</param>
        /// <param name="nonce">随机串12位</param>
        /// <param name="encryptedData">密文（Base64字符）</param>
        /// <param name="associatedData">(可能null)</param>
        /// <returns></returns>
        public static string AesGcmDecryptFromBase64(string key, string nonce, string encryptedData, string associatedData)
        {
            var keyBytes = Convert.FromBase64String(key);
            var nonceBytes = Convert.FromBase64String(nonce);
            var associatedBytes = associatedData == null ? null : Encoding.UTF8.GetBytes(associatedData);

            var encryptedBytes = Convert.FromBase64String(encryptedData);
            //tag size is 16
            var cipherBytes = encryptedBytes[..^16];
            var tag = encryptedBytes[^16..];
            var decryptedData = new byte[cipherBytes.Length];
            using var cipher = new AesGcm(keyBytes);
            cipher.Decrypt(nonceBytes, cipherBytes, tag, decryptedData, associatedBytes);
            return Encoding.UTF8.GetString(decryptedData);
        }

        /// <summary>
        /// 使用 AesGcm AEAD_AES_256_GCM 加密，不要在正式环境中使用这个方法。因为在解密时不知道tag，除非额外返回tag。
        /// </summary>
        /// <param name="key">key32位字符</param>
        /// <param name="nonce">随机串12位</param>
        /// <param name="plainData">明文</param>
        /// <param name="associatedData">附加数据(可能null)</param>
        /// <returns>只返回加密数据不包含authentication tag</returns>
        public static string AesGcmEncryptToBase64(string key, string nonce, string plainData, string associatedData)
        {
            var keyBytes = Convert.FromBase64String(key);
            var nonceBytes = Convert.FromBase64String(nonce);
            var associatedBytes = associatedData == null ? null : Encoding.UTF8.GetBytes(associatedData);

            var plainBytes = Encoding.UTF8.GetBytes(plainData);
            var cipherBytes = new byte[plainBytes.Length];
            using var cipher = new AesGcm(keyBytes);
            //tag size must be is 16 
            var tag = new byte[16];
            cipher.Encrypt(nonceBytes, plainBytes, cipherBytes, tag, associatedBytes);

            return Convert.ToBase64String(cipherBytes);
        }

        /// <summary>
        /// 使用 AesGcm进行AEAD_AES_256_GCM加密
        /// </summary>
        /// <param name="key">key32位字符</param>
        /// <param name="nonce">随机串12位</param>
        /// <param name="plainData">明文</param>
        /// <param name="associatedData">附加数据(可能null)</param>
        /// <returns>base64(加密后数据 + authentication tag)</returns>
        public static (string result,string authTag) AesGcmEncryptToBase64_WithTag(string key, string nonce, string plainData, string associatedData)
        {
            var keyBytes = Convert.FromBase64String(key);
            var nonceBytes = Convert.FromBase64String(nonce);
            var associatedBytes = associatedData == null ? null : Encoding.UTF8.GetBytes(associatedData);

            var plainBytes = Encoding.UTF8.GetBytes(plainData);
            var cipherBytes = new byte[plainBytes.Length];
            //tag size is 16
            var tag = new byte[16];
            using var cipher = new AesGcm(keyBytes);
            cipher.Encrypt(nonceBytes, plainBytes, cipherBytes, tag, associatedBytes);

            return (Convert.ToBase64String(cipherBytes) , Convert.ToBase64String(tag));
        }

        /// <summary>
        /// 签名
        /// </summary>
        /// <param name="private_key_str">私钥</param>
        /// <param name="payload">签名的内容</param>
        /// <returns></returns>
        public static string Sign(string private_key_str,string payload)
        {
            private_key_str = private_key_str.Replace("-----BEGIN PRIVATE KEY-----", "");
            private_key_str = private_key_str.Replace("-----END PRIVATE KEY-----", "");
            private_key_str = Regex.Replace(private_key_str, "\\s+", "");

            var dataBuffer = Encoding.UTF8.GetBytes(payload);
            byte[] decoded = Convert.FromBase64String(private_key_str);


            RsaPrivateCrtKeyParameters privateKey = (RsaPrivateCrtKeyParameters)PrivateKeyFactory.CreateKey(decoded);

            ISigner signer = SignerUtilities.GetSigner("SHA-256withRSAandMGF1");
            signer.Init(true, privateKey);
            signer.BlockUpdate(dataBuffer, 0, dataBuffer.Length);
            byte[] sigBuffer = signer.GenerateSignature();

            return Convert.ToBase64String(sigBuffer);
        }

        public static string GenerateNonce()
        {
            byte[] nonce = GenerateRandomBytes(16);
            return HttpUtility.UrlEncode(Convert.ToBase64String(nonce)).Replace("=", "");
        }

        public static byte[] GenerateRandomBytes(int length)
        {
            byte[] bytes = new byte[length];
            using (RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider())
            {
                rng.GetBytes(bytes);
            }
            return bytes;
        }
    }
}
